/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is Forte for Java, Community Edition. The Initial * Developer of the Original Code is Sun Microsystems, Inc. Portions * Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved. */ package org.openide.options; import java.beans.BeanInfo; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyDescriptor; import java.io.*; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.HashMap; import java.util.HashSet; import java.util.Set; import java.util.Iterator; import java.util.ResourceBundle; import java.text.MessageFormat; import org.openide.util.HelpCtx; import org.openide.util.NbBundle; import org.openide.util.SharedClassObject; /** Base class for all system options. * Provides methods for adding * and working with property change and guarantees * that all instances of the same class will share these listeners. * <P> * When a new option is created, it should subclass * <CODE>SystemOption</CODE>, add <em>static</em> variables to it that will hold * the values of properties, and write non-static setters/getters that will * notify all listeners about property changes via * {@link #firePropertyChange}. * <p>JavaBeans introspection is used to find the properties, * so it is possible to use {@link BeanInfo}. * * @author Jaroslav Tulach */ public abstract class SystemOption extends SharedClassObject { /** generated Serialized Version UID */ static final long serialVersionUID = 558589201969066966L; /** property to indicate that the option is currently loading its data */ private static final Object PROP_LOADING = new Object (); /** property to indicate that the option is currently loading its data */ private static final Object PROP_STORING = new Object (); /** Default constructor. */ public SystemOption() {} /** Fire a property change event to all listeners. Delays * this loading when readExternal is active till it finishes. * * @param name the name of the property * @param oldValue the old value * @param newValue the new value */ protected void firePropertyChange ( String name, Object oldValue, Object newValue ) { if (getProperty (PROP_LOADING) != null) { // somebody is loading, assign any object different than // this to indicate that firing should occure putProperty (PROP_LOADING, PROP_LOADING); // but do not fire the change now return; } super.firePropertyChange (name, oldValue, newValue); } /** Write all properties of this object (or subclasses) to an object output. * @param out the output stream * @exception IOException on error */ public void writeExternal (ObjectOutput out) throws IOException { try { // gets info about all properties that were added by subclass BeanInfo info = org.openide.util.Utilities.getBeanInfo (getClass (), SystemOption.class); PropertyDescriptor[] desc = info.getPropertyDescriptors (); putProperty (PROP_STORING, this); Object[] param = new Object[0]; synchronized (getLock ()) { // write all properties that have getter to stream for (int i = 0; i < desc.length; i++) { String propName = desc[i].getName(); Object value = getProperty(propName); boolean fromRead; if (value == null) { fromRead = true; Method read = desc[i].getReadMethod(); if (read != null) { try { value = read.invoke (this, param); } catch (InvocationTargetException ex) { // exception thrown throw new IOException (new MessageFormat (NbBundle.getBundle (SystemOption.class).getString ("EXC_InGetter")). format (new Object[] {getClass (), desc[i].getName ()}) ); } catch (IllegalAccessException ex) { // exception thrown throw new IOException (new MessageFormat (NbBundle.getBundle (SystemOption.class).getString ("EXC_InGetter")). format (new Object[] {getClass (), desc[i].getName ()}) ); } } } else { fromRead = false; } // writes name of the property out.writeObject (propName); // writes its value out.writeObject (value); // from getter or stored prop? out.writeObject(fromRead ? Boolean.TRUE : Boolean.FALSE); } } } catch (IntrospectionException ex) { // if we cannot found any info about properties } finally { putProperty (PROP_STORING, null); } // write null to signal end of properties out.writeObject (null); } /** Read all properties of this object (or subclasses) from an object input. * If there is a problem setting the value of any property, that property will be ignored; * other properties should still be set. * @param in the input stream * @exception IOException on error * @exception ClassNotFound if a class used to restore the system option is not found */ public void readExternal (ObjectInput in) throws IOException, ClassNotFoundException { synchronized (getLock ()) { // hashtable that maps names of properties to setter methods HashMap map = new HashMap (); try { // indicate that we are loading files putProperty (PROP_LOADING, this); try { // gets info about all properties that were added by subclass BeanInfo info = org.openide.util.Utilities.getBeanInfo (getClass (), SystemOption.class); PropertyDescriptor[] desc = info.getPropertyDescriptors (); // write all properties that have getter to stream for (int i = 0; i < desc.length; i++) { Method m = desc[i].getWriteMethod (); /*if (m == null) { System.out.println ("HOW HOW HOW HOWHOWHOWHOWHWO: " + desc[i].getName() + " XXX " + getClass()); throw new IOException (new MessageFormat (NbBundle.getBundle (SystemOption.class).getString ("EXC_InSetter")). format (new Object[] {getClass (), desc[i].getName ()}) ); } */ map.put (desc[i].getName (), m ); } } catch (IntrospectionException ex) { // if we cannot found any info about properties // leave the hashtable empty and only read stream till null is found } String preread = null; do { // read the name of property String name; if (preread != null) { name = preread; preread = null; } else { name = (String)in.readObject(); } // break if the end of property stream is found if (name == null) break; // read the value of property Object value = in.readObject (); // read flag - use the setter method or store as property? Object useMethodObject = in.readObject(); boolean useMethod; boolean nullRead = false; // this should be last processed property? if (useMethodObject == null) { useMethod = true; nullRead = true; } else if (useMethodObject instanceof String) { useMethod = true; preread = (String) useMethodObject; } else { useMethod = ((Boolean) useMethodObject).booleanValue(); } if (useMethod) { // set the value Method write = (Method)map.get (name); if (write != null) { // if you have where to set the value try { write.invoke (this, new Object[] { value }); } catch (InvocationTargetException ex) { } catch (IllegalAccessException ex) { } catch (IllegalArgumentException ex) { } } } else { putProperty(name, value, false); } if (nullRead) { break; } } while (true); } finally { // get current state if (this != getProperty (PROP_LOADING)) { // some changes should be fired // loading finished putProperty (PROP_LOADING, null); firePropertyChange (null, null, null); } else { // loading finished putProperty (PROP_LOADING, null); } } } } /** * Get the name of this system option. * The default implementation just uses the {@link #displayName display name}. * @return the name */ public final String getName () { return displayName (); } /** * Get the display name of this system option. * @return the display name */ public abstract String displayName (); /** Get context help for this system option. * @return context help */ public HelpCtx getHelpCtx () { return new HelpCtx (SystemOption.class); } /** Allows subclasses to test whether the change of a property * is invoked from readExternal method or by external change invoked * by any other program. * * @return true if the readExternal method is in progress */ protected final boolean isReadExternal () { return getProperty (PROP_LOADING) != null; } /** Allows subclasses to test whether the getter of a property * is invoked from writeExternal method or by any other part of the program. * * @return true if the writeExternal method is in progress */ protected final boolean isWriteExternal () { return getProperty (PROP_STORING) != null; } } /* * Log * 16 Gandalf 1.15 12/23/99 Ales Novak #5107 * 15 Gandalf 1.14 10/22/99 Ian Formanek NO SEMANTIC CHANGE - Sun * Microsystems Copyright in File Comment * 14 Gandalf 1.13 10/4/99 Jaroslav Tulach Has also * isWriteExternal. * 13 Gandalf 1.12 9/30/99 Jaroslav Tulach DataLoader is now * serializable. * 12 Gandalf 1.11 6/30/99 Jesse Glick Context help. * 11 Gandalf 1.10 6/22/99 Jaroslav Tulach Does not fire change * when loading properties. * 10 Gandalf 1.9 6/8/99 Ian Formanek ---- Package Change To * org.openide ---- * 9 Gandalf 1.8 5/12/99 Ales Novak options can be RD_ONLY * 8 Gandalf 1.7 4/20/99 Jesse Glick Made firePropertyChange * final again--there should be no reason to override it, that would * probably just be a confusing option. * 7 Gandalf 1.6 4/20/99 Jesse Glick SystemOption.firePropertyChange * is now protected and unfinal. * 6 Gandalf 1.5 3/26/99 Ian Formanek Fixed use of obsoleted * NbBundle.getBundle (this) * 5 Gandalf 1.4 3/22/99 Jesse Glick [JavaDoc] * 4 Gandalf 1.3 3/8/99 Jaroslav Tulach Bundles. * 3 Gandalf 1.2 3/4/99 Jan Jancura * 2 Gandalf 1.1 3/4/99 Petr Hamernik * 1 Gandalf 1.0 1/5/99 Ian Formanek * $ * Beta Change History: * 0 Tuborg 0.11 --/--/98 Jan Jancura getter for mane of options. * 0 Tuborg 0.12 --/--/98 Jaroslav Tulach changed fired exception */